﻿using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Nito.AsyncEx;


namespace AuthZ.PolicyBased.Authorizations.AuthorizeAttributes;

/// <summary>
/// 注意：只能注册一个 IAuthorizationPolicyProvider，所以要将所有逻辑写到一个类中
/// </summary>
public class AppAuthorizationPolicyProvider : IAuthorizationPolicyProvider
{
    private static readonly AsyncLock _mutex = new();
    private readonly AuthorizationOptions _authorizationOptions;
    private DefaultAuthorizationPolicyProvider BackupPolicyProvider { get; }
        
    public AppAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)
    {
        BackupPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
        _authorizationOptions = options.Value;
    }

        
    /// <inheritdoc />
    public async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
    {
        if (policyName is null) throw new ArgumentNullException(nameof(policyName));

        var policy = await BackupPolicyProvider.GetPolicyAsync(policyName);
        if (policy is not null) return policy;
            
        using (await _mutex.LockAsync())
        {
            policy = await BackupPolicyProvider.GetPolicyAsync(policyName);
            if (policy is not null) return policy;

            if (policyName.StartsWith(MinimumAgeAuthorizeAttribute.PolicyPrefix, StringComparison.OrdinalIgnoreCase)
                && int.TryParse(policyName[MinimumAgeAuthorizeAttribute.PolicyPrefix.Length..], out var age))
            {
                var builder = new AuthorizationPolicyBuilder();
                builder.RequireMinimumAge(age);
                policy = builder.Build();
                _authorizationOptions.AddPolicy(policyName, policy);
                return policy;
            }
        }

        return null;
    }

    /// <inheritdoc />
    public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
    {
        return BackupPolicyProvider.GetDefaultPolicyAsync();
    }

    /// <inheritdoc />
    public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
    {
        return BackupPolicyProvider.GetFallbackPolicyAsync();
    }
}